package pl.edu.agh.iosr.gamblingzone.dao.ejb;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;

import pl.edu.agh.iosr.gamblingzone.dao.GenericDAO;
import pl.edu.agh.iosr.gamblingzone.model.EntityModel;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import java.util.LinkedList;
import java.util.List;

/**
 * The Class GenericDAOBean.
 */
public abstract class GenericDAOBean<T,ID extends Serializable>
		implements GenericDAO<T, ID> {
	
	/** The page size. */
	private int pageSize = 10;
	
	/** The entity bean type. */
	private Class<T> entityBeanType;

	/** The entity manager. */
	private EntityManager entityManager;
	
	/**
	 * Instantiates a new generic dao bean.
	 */
	@SuppressWarnings("unchecked")
	public GenericDAOBean() {
		this.entityBeanType = (Class<T>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
	}
	
	/**
	 * Sets the entity manager.
	 * 
	 * @param entityManager the new entity manager
	 */
	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}
	
	/**
	 * Gets the entity manager.
	 * 
	 * @return the entity manager
	 */
	protected EntityManager getEntityManager() {
		if (entityManager == null)
		    throw new IllegalStateException("EntityManager has not been set on DAO before usage");
		return entityManager;
	}
	
	/**
	 * Gets the entity bean type.
	 * 
	 * @return the entity bean type
	 */
	public Class<T> getEntityBeanType() {
		return entityBeanType;
	}
	
	/* (non-Javadoc)
	 * @see pl.edu.agh.iosr.gamblingzone.dao.GenericDAO#findById(java.io.Serializable, boolean)
	 */
	public T findById(ID id, boolean lock) {
		T entity;
		if (lock) {
		    entity = getEntityManager().find(getEntityBeanType(), id);
		    entityManager.lock(entity, javax.persistence.LockModeType.WRITE);
		} else {
		    entity = getEntityManager().find(getEntityBeanType(), id);
		}
		return entity;
	}
	
	/* (non-Javadoc)
	 * @see pl.edu.agh.iosr.gamblingzone.dao.GenericDAO#findAll()
	 */
	@SuppressWarnings("unchecked")
	public List<T> findAll() {
		List<T> res = getEntityManager().createQuery("from " + getEntityBeanType().getName() ).getResultList();
		
		if (res == null) {
			return new LinkedList<T>();
		}
		
		return res;
	}
	
	/* (non-Javadoc)
	 * @see pl.edu.agh.iosr.gamblingzone.dao.GenericDAO#findAll(int)
	 */
	@SuppressWarnings("unchecked")
	public List<T> findAll(int page) {
		List<T> resList =  getEntityManager()
				.createQuery("from " + getEntityBeanType().getName())
					.setFirstResult(page*getPageSize())
					.setMaxResults(getPageSize())
				.getResultList();
		
		if (resList == null) {
			return new LinkedList<T>();
		}
		
		return resList;
	}
	
	/**
	 * Find all.
	 * 
	 * @param entityParam the entity param
	 * @param fieldName the field name
	 * @param page the page
	 * 
	 * @return the list< t>
	 */
	@SuppressWarnings("unchecked")
	public List<T> findAll(EntityModel entityParam, String fieldName, int page) {

		EntityModel entities[] =  { entityParam };
		String entitesFields[] = { fieldName };
		
		return findAll(entities, entitesFields, page);
	}
	
	/**
	 * Find all.
	 * 
	 * @param entityParams the entity params
	 * @param fieldNames the field names
	 * @param page the page
	 * 
	 * @return the list< t>
	 */
	@SuppressWarnings("unchecked")
	public List<T> findAll(EntityModel entityParams[], String fieldNames[], int page) {
		
		if (entityParams == null || fieldNames == null) {
			throw new NullPointerException("Arguments shouldn't be null");
		}
		
		if (entityParams.length != fieldNames.length) {
			throw new IllegalStateException("Dimension of arrays doesn't match");
		}
		
		String strQuery = "select x from " + getEntityBeanType().getName()+ " x where ";
		
		for (int i = 0; i < fieldNames.length; i++) {
			strQuery += " x."+fieldNames[i]+" = :param"+i;
		}

		Query query = getEntityManager().createQuery(strQuery);
		
		for (int i = 0; i < fieldNames.length; i++) {
			query.setParameter("param"+i, entityParams[i]);
		}
		
		List<T> resList =  query
					.setFirstResult(page*getPageSize())
					.setMaxResults(getPageSize())
				.getResultList();
		
		if (resList == null) {
			return new LinkedList<T>();
		}
		
		return resList;
	}
	
	/* (non-Javadoc)
	 * @see pl.edu.agh.iosr.gamblingzone.dao.GenericDAO#getEntitiesCount()
	 */
	public int getEntitiesCount() {
		Number count = (Number)getEntityManager()
			.createQuery("select count(x) from " + getEntityBeanType().getName() +" x ")
			.getSingleResult();

		if (count == null) {
			return 0;
		}
		
		return count.intValue();
	}
	
	/**
	 * Gets the entities count.
	 * 
	 * @param entityParam the entity param
	 * @param fieldName the field name
	 * 
	 * @return the entities count
	 */
	protected int getEntitiesCount(EntityModel entityParam, String fieldName) {
		
		EntityModel[] entityParams = { entityParam };
		String[] fieldNames = { fieldName };
		
		return getEntitiesCount(entityParams, fieldNames);
	}
	
	/**
	 * Gets the entities count.
	 * 
	 * @param entityParams the entity params
	 * @param fieldNames the field names
	 * 
	 * @return the entities count
	 */
	protected int getEntitiesCount(EntityModel entityParams[], String fieldNames[]) {
		
		if (entityParams == null || fieldNames == null) {
			throw new NullPointerException("Arguments shouldn't be null");
		}
		
		if (entityParams.length != fieldNames.length) {
			throw new IllegalStateException("Dimension of arrays doesn't match");
		}
		
		String strQuery = "select count(x) from " + getEntityBeanType().getName()+ " x where ";
		
		for (int i = 0; i < fieldNames.length; i++) {
			strQuery += " x."+fieldNames[i]+" = :param"+i;
		}

		Query query = getEntityManager().createQuery(strQuery);
		
		for (int i = 0; i < fieldNames.length; i++) {
			query.setParameter("param"+i, entityParams[i]);
		}
		
		Number count = (Number)query.getSingleResult();
		
		if (count == null) {
			return 0;
		}

		return count.intValue();
	}
	
	/* (non-Javadoc)
	 * @see pl.edu.agh.iosr.gamblingzone.dao.GenericDAO#getPageCount()
	 */
	public int getPageCount() {
		
		int userCount = getEntitiesCount();
		
		int retNum = userCount/pageSize;
		if (userCount%pageSize > 0) {
			retNum++;
		}
		
		return retNum;
	}
	
	/**
	 * Gets the page count.
	 * 
	 * @param entityParam the entity param
	 * @param fieldName the field name
	 * 
	 * @return the page count
	 */
	protected int getPageCount(EntityModel entityParam, String fieldName) {
		
		int enCount = getEntitiesCount(entityParam, fieldName);
		
		int retNum = enCount/pageSize;
		if (enCount%pageSize > 0) {
			retNum++;
		}

		return retNum;
	}
	
	/* (non-Javadoc)
	 * @see pl.edu.agh.iosr.gamblingzone.dao.GenericDAO#makePersistent(java.lang.Object)
	 */
	public T makePersistent(T entity) {
		return getEntityManager().merge(entity);
	}
	
	/* (non-Javadoc)
	 * @see pl.edu.agh.iosr.gamblingzone.dao.GenericDAO#makeTransient(java.lang.Object)
	 */
	public void makeTransient(T entity) {
		getEntityManager().remove(entity);
	}
	
	/**
	 * Flush.
	 */
	public void flush() {
		getEntityManager().flush();
	}
	
	/**
	 * Clear.
	 */
	public void clear() {
		getEntityManager().clear();
	}

	/**
	 * Gets the page size. Page is a set of results.
	 * 
	 * @return the page size
	 */
	public int getPageSize() {
		return pageSize;
	}

	/**
	 * Sets the page size.
	 * 
	 * @param pageSize the new page size
	 */
	public void setPageSize(int pageSize) {
		this.pageSize = pageSize;
	}
}
